﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace wpfTool
{
    struct ShaderConnectionInfoElement
    {
        public ShaderBase shader;
        public int portIndex;

        public ShaderConnectionInfoElement(ShaderBase sdr, int index)
        {
            shader = sdr;
            portIndex = index;
        }
    }

    class ShaderConnectionInfo
    {
        public ShaderBase shader;
        public SortedDictionary<int, LinkedList<ShaderConnectionInfoElement>> portConnectionTable;

        public ShaderConnectionInfo(ShaderBase sdr)
        {
            shader = sdr;

            if (!sdr.IsDst)
            {
                portConnectionTable = new SortedDictionary<int, LinkedList<ShaderConnectionInfoElement>>();

                for (int i = 0; i < shader.OutputPortsList.Length; ++i)
                {
                    portConnectionTable[i] = new LinkedList<ShaderConnectionInfoElement>();
                }
            }
        }

        public int portsCount
        {
            get { return shader.OutputPortsList.Length; }
        }

        public void addConnection(int srcIndex, ShaderBase dstShader, int dstIndex)
        {
            foreach (ShaderConnectionInfoElement sce in portConnectionTable[srcIndex])
            {
                if (sce.shader == dstShader && sce.portIndex == dstIndex)
                    return;
            }

            portConnectionTable[srcIndex].AddLast(new ShaderConnectionInfoElement(dstShader, dstIndex));
        }
    }

    public class ShaderManager
    {
        Window1 MainWindow;

        string plugPath = "D:\\Source\\wpfTool\\BasicShaders\\bin\\Release";

        /// <summary>
        /// All the shaders connection infos,
        /// are stored here.
        /// The first level key is the pipeline ref;
        /// The second level Key is the src shader;
        /// Values are all the dst shaders which connected to 
        /// the src shader.
        /// </summary>
        SortedDictionary<PipelineView,
            SortedDictionary<ShaderBase,
            ShaderConnectionInfo>> connectionDB = null;

        SortedDictionary<string, Type> shaderDB = null;

        bool previewMode = true;

        internal bool IsPreviewMode
        {
            get { return previewMode; }
            set { previewMode = value; }
        }


        public ShaderManager(Window1 wnd)
        {
            MainWindow = wnd;
            connectionDB = new SortedDictionary<PipelineView,
                SortedDictionary<ShaderBase, ShaderConnectionInfo>>();

            shaderDB = new SortedDictionary<string, Type>();

            loadPlugShader(plugPath + "\\basicshaders.dll");
        }


        internal SortedDictionary<string, Type> ShaderDB
        {
            get { return shaderDB; }
        }

        internal void updatePipeline(PipelineView pv)
        {
            LinkedList<ShaderBase> buffer = new LinkedList<ShaderBase>();

            // collect all the source shader
            foreach (ShaderBase sc in connectionDB[pv].Keys)
            {
                if (sc.IsSrc && buffer.Find(sc) == null)
                    buffer.AddLast(sc);
            }

            while (buffer.Count != 0)
            {

                foreach (ShaderBase sc in buffer)
                {
                    // after the techniqueProcess, all the 
                    // shader.InputObjectMarks are set to false
                    sc.techniqueProcess();
                }

                LinkedList<ShaderBase> temp = new LinkedList<ShaderBase>(buffer);
                buffer.Clear();

                foreach (ShaderBase shader_src in temp)
                {
                    if (shader_src.IsDst)
                        continue;

                    for (int i = 0; i < connectionDB[pv][shader_src].portsCount; ++i)
                    {
                        foreach (ShaderConnectionInfoElement sc_dst
                            in connectionDB[pv][shader_src].portConnectionTable[i])
                        {
                            sc_dst.shader.InputObjects[sc_dst.portIndex]
                                = shader_src.OutputObjects[i];

                            // mark input port in dst shader
                            sc_dst.shader.InputObjectMarks[sc_dst.portIndex] = true;

                            if (sc_dst.shader.ReadyToUpdate == true && buffer.Find(sc_dst.shader) == null)
                                buffer.AddLast(sc_dst.shader);
                        }
                    }
                }
            }
        }

        internal void updatePipeline(PipelineView pv, ShaderBase startShader)
        {
            LinkedList<ShaderBase> buffer = new LinkedList<ShaderBase>();
            buffer.AddLast(startShader);

            while (buffer.Count != 0)
            {
                foreach (ShaderBase sc in buffer)
                {
                    if(!sc.IsDst)
                        sc.techniqueProcess();
                }

                LinkedList<ShaderBase> temp = new LinkedList<ShaderBase>(buffer);
                buffer.Clear();

                foreach (ShaderBase shader_src in temp)
                {
                    if (shader_src.IsDst)
                        continue;

                    for (int i = 0; i < connectionDB[pv][shader_src].portsCount; ++i)
                    {
                        foreach (ShaderConnectionInfoElement sc_dst in
                            connectionDB[pv][shader_src].portConnectionTable[i])
                        {
                            sc_dst.shader.InputObjects[sc_dst.portIndex]
                                = shader_src.OutputObjects[i];

                            // mark input port in dst shader
                            sc_dst.shader.InputObjectMarks[sc_dst.portIndex] = true;

                            // the next iteration buffer
                            if (buffer.Find(sc_dst.shader) == null && sc_dst.shader.ReadyToUpdate == true)
                                buffer.AddLast(sc_dst.shader);
                        }
                    }
                }
            }
        }

        internal void updatePipeline(PipelineView pv, ShaderBase src, int srcIndex, ShaderBase dst, int dstIndex)
        {
            dst.InputObjects[dstIndex] = src.OutputObjects[srcIndex];
            updatePipeline(pv, dst);
        }


        internal void OnCreateShaderEventHandler(PipelineView pv, string shaderName, out ShaderBase shader)
        {
            shader = (ShaderBase)(System.Activator.CreateInstance(shaderDB[shaderName]));
            addShaderToPipeline(pv, shader);
            MainWindow.propertyGridMgr.createShaderPropertyGrid(shader);
        }

        void loadPlugShader(string path)
        {
            System.Reflection.Assembly assemblyShader = System.Reflection.Assembly.LoadFrom(path);

            System.Type[] shaderTypeArray = assemblyShader.GetTypes();

            for (int i = 0; i < shaderTypeArray.Length; ++i)
            {
                object temp = System.Activator.CreateInstance(shaderTypeArray[i]);
                shaderDB[(temp as ShaderBase).ShaderName] = shaderTypeArray[i];

                System.Windows.Controls.ListBoxItem lbi = new System.Windows.Controls.ListBoxItem();
                System.Windows.Controls.Label l = new System.Windows.Controls.Label();
                l.Content = (temp as ShaderBase).ShaderName;
                lbi.Content = l;
                lbi.Selected += new System.Windows.RoutedEventHandler(ListBoxItem_Selected);
                MainWindow.theShaderListBox.Items.Add(lbi);
            }
        }

        void ListBoxItem_Selected(object sender, System.Windows.RoutedEventArgs e)
        {
            string shaderName = ((sender as System.Windows.Controls.ListBoxItem).Content
                as System.Windows.Controls.Label).Content as string;
            MainWindow.ReadyToAddShaderName = shaderName;
        }

        internal void addPipelineView(PipelineView pv)
        {
            if (connectionDB.ContainsKey(pv))
                return;

            connectionDB[pv] = new SortedDictionary<ShaderBase, ShaderConnectionInfo>();
        }

        void addShaderToPipeline(PipelineView pv, ShaderBase shader)
        {
            if (connectionDB.ContainsKey(pv) && !connectionDB[pv].ContainsKey(shader))
            {
                connectionDB[pv][shader] = new ShaderConnectionInfo(shader);
            }
        }

        internal void OnAddShaderConnectionEventHandler(PipelineView pv, ShaderBase srcShader, int srcPortIndex, ShaderBase dstShader, int dstPortIndex)
        {
            if (connectionDB.ContainsKey(pv))
            {
                connectionDB[pv][srcShader].addConnection(srcPortIndex, dstShader, dstPortIndex);
            }
        }

        internal void PreviewMode_ToggleButton_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            IsPreviewMode = !IsPreviewMode;

            foreach (PipelineView pv in connectionDB.Keys)
            {
                foreach (ShaderBase sb in connectionDB[pv].Keys)
                {
                    if (sb.IsSrc)
                        sb.usePreviewBitmap = IsPreviewMode;
                }
            }
        }

    }
}
